package com.gitee.l0km.beanfilter.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.beanfilter.core.context.LocalContext;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;

import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.base.MoreObjects.firstNonNull;;

/**
 * 基于请求上下文{@link LocalContext}实现的字段过滤器<br>
 * 构造方法提供的字段名数组支持通过前缀指定字段名适用的编码阶段( {@link CodecPhase} )和场景({@link CodecScope })
 * @author guyadong
 *
 */
public class FlexibleContextFilter extends BaseFieldFilter {
	private static final String[] EMPTY_STRING_ARRAY = new String[0];
	private static final String META_REGEX = "^(?:((?:NO|BO|SE|DE)\\w*?)?(?:/((?:NO|AL|TH|JS|JA|FA)\\w*?)?)?:)?(\\w+(?:,\\w+)*)$";
	final Set<NameMeta>  excludeMetas;
	final Set<NameMeta> includeMetas;
	
	public FlexibleContextFilter(Class<?> beanClass,boolean autoCase,String[] excludeNames, String[] includeNames) {
		this(beanClass, autoCase, null == excludeNames || excludeNames.length ==0, excludeNames, includeNames, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, true);
	}

	/**
	 * {@code @com.gitee.l0km.beanfilter.core.annotations.FlexibleNameFilter} 注解定义的 构造方法
	 * @param beanClass Java Bean类型
	 * @param autoCase 是否将snake-case和camel-case格式的字段字视为同一字段，如firstName和first_name,为{@code true}时视为同一个名字。
	 * @param whiteList 为{@code true}时为白名单模式，{@code filterNames}指定字段名被允许输入/输出，否则为黑名单模式，{@code filterNames}指定字段名被禁止输入/输出
	 * @param filterNames 过滤的字段名列表
	 * @param activeOnClasses 过滤器激活器类型列表，必须为{@link Activation}接口实现
	 * @param activeOnClassNames  过滤器激活器类名列表，必须为{@link Activation}接口实现类名,与{@code activeOnClasses}参数作用相同，用字符器定义类名在特定场景可以减少依赖
	 * @param and 指定多个过滤器激活器时，激活判断模式，为{@code true}为AND模式，所有激活器条件都匹配才能激活过滤器，否则为OR模式，任意一激活器条件都匹配就可以激活过滤器
	 */
	public FlexibleContextFilter(Class<?> beanClass, boolean autoCase, boolean whiteList, String[] filterNames,
			Class<?>[] activeOnClasses, String[] activeOnClassNames, boolean and) {
		this(beanClass, autoCase,
				whiteList, 
				whiteList ? EMPTY_STRING_ARRAY : filterNames, 
				whiteList ?  filterNames: EMPTY_STRING_ARRAY, activeOnClasses, activeOnClassNames, and);
	}

	protected FlexibleContextFilter(Class<?> beanClass, boolean autoCase, boolean whiteList, String[] excludeNames,
			String[] includeNames, Class<?>[] activeOnClasses, String[] activeOnClassNames, boolean and) {
		super(beanClass, autoCase, whiteList, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, activeOnClasses, activeOnClassNames, and);
		this.excludeMetas = parse(firstNonNull(excludeNames,EMPTY_STRING_ARRAY));
		this.includeMetas = parse(firstNonNull(includeNames,EMPTY_STRING_ARRAY));
	}
	@Override
	public Set<String> getExcludeNames() {
		return dynamicNamesOf(excludeMetas);
	}
	@Override
	public Set<String> getIncludeNames() {
		return dynamicNamesOf(includeMetas);
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + Objects.hash(excludeMetas, includeMetas);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		FlexibleContextFilter other = (FlexibleContextFilter) obj;
		return Objects.equals(excludeMetas, other.excludeMetas) && Objects.equals(includeMetas, other.includeMetas);
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("FlexibleContextFilter [excludeMetas=").append(excludeMetas).append(", includeMetas=")
				.append(includeMetas).append("]");
		return builder.toString();
	}

	/**
	 * 根据上下文 {@link LocalContext}返回 {@code metas}中定义的字段名
	 * @param metas
	 */
	private static Set<String> dynamicNamesOf(Set<NameMeta>  metas){
		LocalContext ctx = LocalContext.getCurrentContext();
		return namesOf(ctx.getRequestPhase().phase, ctx.getScene().scope,metas);
	}
	@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	static Set<String> namesOf(CodecPhase phase,CodecScope scope,Set<NameMeta>  metas){
		phase =firstNonNull(phase,CodecPhase.NONE);
		scope =firstNonNull(scope,CodecScope.NONE);
		if(phase.equals(CodecPhase.NONE) || scope.equals(CodecScope.NONE)) {
			return Collections.emptySet();
		}
		Set<String> result = new LinkedHashSet<>();
		for(Set<String> names: FluentIterable.from(metas)
				.filter(new MetaMatcher(phase, scope))
				.transform(NAMES_OF_META_FUN)) {
			result.addAll(names);
		}
		return Collections.unmodifiableSet(result);		
	}
	/**
	 * 将{@code input}用{@code delim}指定的分隔符切分为不含空格和分隔符的一组字符串
	 * @param input 输入字符串
	 * @param delim 包含多个分隔符的字符串
	 * @return {@code input}或{@code delim}为{@code null}时返回空表
	 */
	private static final List<String> elementsOf(String input,String delim) {
		List<String> list = new ArrayList<String>();
		if (input != null && delim != null) {
			StringTokenizer st = new StringTokenizer(input, delim);
			while (st.hasMoreTokens()) {
				list.add(st.nextToken());
			}
		}
		return list;
	}
	private static CodecPhase parsePhase(String input) {
		for(CodecPhase element:CodecPhase.values()) {
			if(element.name().startsWith(input)) {
				return element;
			}
		}
		throw new IllegalArgumentException("INVALID parse "+ input);
	}
	private static CodecScope parseScope(String input) {
		for(CodecScope element:CodecScope.values()) {
			if(element.name().startsWith(input)) {
				return element;
			}
		}
		throw new IllegalArgumentException("INVALID scope: "+ input);
	}
	private static final Function<String, NameMeta> NAME_META_FUN = new Function<String, NameMeta>(){

		@Override
		public NameMeta apply(String input) {
			return parse(input);
		}};
	private static final Function<NameMeta, Set<String>> NAMES_OF_META_FUN = new Function<NameMeta, Set<String>>(){
		
		@Override
		public Set<String> apply(NameMeta input) {
			return input.names;
		}};
	static NameMeta parse(String input) {
		input = CharMatcher.whitespace().removeFrom(nullToEmpty(input));
		if(!input.isEmpty()) {
			Matcher matcher= Pattern.compile(META_REGEX).matcher(input);
			if(matcher.find()) {
				String g1 =  matcher.group(1);
				CodecPhase parse = g1==null ?CodecPhase.BOTH:parsePhase(g1);
				String g2 = matcher.group(2);
				CodecScope scope = g2==null ?CodecScope.ALL:parseScope(g2);
				List<String> names =  elementsOf(matcher.group(3),",");
				return new NameMeta(parse,scope,names);
			}
		}
		return null;
	}
	static Set<NameMeta> parse(String[] input) {
		LinkedHashSet<NameMeta> metas = FluentIterable.from(input)
			.transform(NAME_META_FUN)
			.filter(Predicates.notNull())
			.copyInto(new LinkedHashSet<NameMeta>());
		return Collections.unmodifiableSet(metas);
	}
	private static class MetaMatcher implements Predicate<NameMeta>{
		final CodecPhase phase;
		final CodecScope scope;
		MetaMatcher(CodecPhase phase, CodecScope scope) {
			this.phase = phase;
			this.scope = scope;
		}
		@Override
		public boolean apply(NameMeta input) {
			return phase.match(input.phase)&& scope.match(input.scope);
		}
		@Override
		public int hashCode() {
			return Objects.hash(phase, scope);
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			MetaMatcher other = (MetaMatcher) obj;
			return phase == other.phase && scope == other.scope;
		}
		
	}
	static class NameMeta{
		final CodecPhase phase;
		final CodecScope scope;
		final Set<String> names;
		NameMeta(CodecPhase phase, CodecScope scope, Collection<String> names) {
			this.phase = phase;
			this.scope = scope;
			this.names = Collections.unmodifiableSet(new LinkedHashSet<>(names));
		}
		@Override
		public int hashCode() {
			return Objects.hash(names, phase, scope);
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			NameMeta other = (NameMeta) obj;
			return Objects.equals(names, other.names) && phase == other.phase && scope == other.scope;
		}
		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			builder.append("NameMeta [phase=").append(phase).append(", scope=").append(scope).append(", names=")
					.append(names).append("]");
			return builder.toString();
		}

	}
}
